﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Collections.ObjectModel;
using Medianamik.Core.Cache;
using Medianamik.Core.Urls;
using Medianamik.Core.Interfaces;
using System.IO;
using System.Text.RegularExpressions;
using System.Reflection;
using ICSharpCode.SharpZipLib.Zip;
using StructureMap;

namespace Medianamik.Core
{
    public static class ConfigManager
    {
        //private static readonly MedianamikConfigSection Config =
        //    (MedianamikConfigSection)ConfigurationManager.GetSection(ConfigurationSectionName.MEDIANAMIK);


        public static IEnumerable<T> GetAll<T>(CultureInfo culture)
            where T : INode, INodeWrapper, ISystemNode, new()
        {
            IEnumerable<T> nodes = GetAll<T>();
            nodes.ForEach(n => n.DefaultCulture = culture);
            return nodes;
        }

        public static IEnumerable<T> GetAll<T>()
            where T : INodeWrapper, ISystemNode, new()
        {
            return NodeManager.GetNodesByType(new T().SystemNodeTypeID)
                .Select(n => new T { InnerNode = n });
        }

        public static T Get<T>(Guid pNodeId)
            where T : INodeWrapper, ISystemNode, new()
        {
            return NodeManager.GetNode<T>(pNodeId, new T().SystemNodeTypeID);
        }

        internal static ConfigProvider Provider
        {
            get { return ConfigProvider.Instance; }
        }

        #region Module
        
        public static MedianamikModuleDTO GetModuleByAlias(string alias, CultureInfo culture)
        {
            return ObjectFactory.GetInstance<IMedianamikModuleRepository>().GetByAlias(alias, culture);
        }
        
        #endregion

        #region Culture

        public static Culture GetCulture(string pCultureName)
        {
            return GetAllCultures().FirstOrDefault(c => c.CultureName == pCultureName);
        }

        public static IEnumerable<Culture> GetMedianamikCultures()
        {
            return CultureCache.Instance.Get("GetMedianamikCultures"
                                             , () =>
                                                   {
                                                       return DALProviderManager.DALProvider.ConfigDALProvider.
                                                               GetMedianamikCultures()
                                                               .OrderBy(c => c.Position).AsEnumerable();
                                                   });
        }

        public static IEnumerable<Culture> GetEditionCultures()
        {
            return CultureCache.Instance.Get("GetEditionCultures"
                                       , () =>
                                             {
                                                 return DALProviderManager.DALProvider.ConfigDALProvider.GetEditionCultures()
                                                         .OrderBy(c => c.Position).AsEnumerable();
                                             });
        }

        public static IEnumerable<Culture> GetAllCultures()
        {
            return CultureCache.Instance.Get("GetAllCultures"
                                             , () =>
                                                   {
                                                       return DALProviderManager.DALProvider.ConfigDALProvider.
                                                               GetAllCultures()
                                                               .OrderBy(c => c.Position).AsEnumerable();
                                                   });
        }

        public static Culture SaveCulture(Culture pCulture)
        {
            var culture = DALProviderManager.DALProvider.ConfigDALProvider.SaveCulture(pCulture);
            ClearCultureCache();
            return culture;
        }

        public static void ClearCultureCache()
        {
            CultureCache.Instance.Clear();
        }

        private class CultureCache : CacheManagerBase<CultureCache>
        {
            protected override string CacheKeyPrefix
            {
                get { return "CultureCache"; }
            }
        }

        #endregion

        #region Controls

        public static Collection<MedianamikControl> GetMedianamikControls()
        {
            return GetMedianamikControls(null, null);
        }

        public static Collection<MedianamikControl> GetMedianamikControls(Guid? pDataTypeId)
        {
            return GetMedianamikControls(pDataTypeId, null);
        }

        public static Collection<MedianamikControl> GetMedianamikControls(Guid? pDataTypeId, MedianamikControlType? pControlType)
        {
            return new Collection<MedianamikControl>(
                DALProviderManager.DALProvider.ConfigDALProvider.
                GetMedianamikControls(pDataTypeId, pControlType));
        }

        public static MedianamikControl GetMedianamikControl(Guid pMedianamikControlID)
        {
            return DALProviderManager.DALProvider.ConfigDALProvider.GetMedianamikControl(pMedianamikControlID);
        }

        //public static MedianamikControl NewMedianamikControl()

        public static void DeleteMedianamikControl(Guid controlId)
        {
            DALProviderManager.DALProvider.ConfigDALProvider.DeleteMedianamikControl(controlId);
        }

        #endregion

        #region WebSite

        public static Collection<WebSite> GetAllWebSites(CultureInfo pCulture)
        {
            List<WebSite> sites = NodeManager.GetNodesByType<WebSite>(false);
            sites.ForEach(s => s.DefaultCulture = pCulture);
            return new Collection<WebSite>(sites);
        }

        public static WebSite GetWebSite(Guid pSiteID, CultureInfo pCulture)
        {
            WebSite site = NodeManager.GetNode<WebSite>(pSiteID);
            site.DefaultCulture = pCulture;
            return site;
        }

        public static WebSite NewWebSite(string pSiteName, CultureInfo pCulture)
        {
            if (pCulture == null) { throw new ArgumentNullException("pCulture"); }
            if (string.IsNullOrEmpty(pSiteName)) { throw new ArgumentException("pSiteName cannot be 'null' or Empty"); }
            WebSite site = NodeManager.NewNode<WebSite>(pCulture);
            site.Instances[pCulture].Properties["Name"].SetValue(pSiteName);
            return site;
        }

        public static WebSite SaveWebSite(WebSite pWebSite)
        {
            return NodeManager.SaveNode(pWebSite);
        }

        #endregion

        #region DataType

        public static ReadOnlyCollection<DataType> GetAllDataTypes()
        {
            return DALProviderManager.DALProvider.ConfigDALProvider.GetAllDataTypes().AsReadOnly();
        }

        public static DataType GetDataTypeById(Guid pDataTypeId)
        {
            return DALProviderManager.DALProvider.ConfigDALProvider.GetDataTypeById(pDataTypeId);
        }

        #endregion

        #region UrlDictionary

        public static List<IUrlDictionaryEntry> GetUrlDictionaryEntries()
        {
            //Important que les entries soient ordonnées par generation - si le draft est identique au published - ajouter le published (premier)
            //Voir implémentation de UrlRepository
            var dbEntries = ObjectFactory.GetInstance<IUrlRepository>().GetUrlXmlDocs();
            dbEntries.OrderByDescending(e => e.Generation); 

            //!Important les domaines doivent être ordonnés par position
            //Cela détermine leur priorité dans le UrlDictionary
            var domains = GetAll<Domain>().OrderBy(d => d.LongPosition).ToList();
            var pageTemplates = GetAll<PageTemplate>().ToList();

            var entries = new List<IUrlDictionaryEntry>();
            dbEntries.ForEach(e => entries.AddRange(e.GetIUrlDictionaryEntries(domains, pageTemplates)));

            return entries;
        }

        public static List<IUrlDictionaryEntry> GetUrlDictionaryEntries(Guid pNodeId)
        {
            var dbEntries = ObjectFactory.GetInstance<IUrlRepository>().GetUrlXmlDocs(pNodeId);

            //!Important les domaines doivent être ordonnés par position
            //Cela détermine leur priorité dans le UrlDictionary
            var domains = GetAll<Domain>().OrderBy(d => d.LongPosition).ToList();
            var pageTemplates = GetAll<PageTemplate>().ToList();

            var entries = new List<IUrlDictionaryEntry>();
            dbEntries.ForEach((e) => entries.AddRange(e.GetIUrlDictionaryEntries(domains, pageTemplates)));

            return entries;
        }

        #endregion

        #region Templates


        #endregion

        #region DatabaseVersioning

        public static Collection<ISchemaChange> GetSchemaChangesApplied()
        {
            return new Collection<ISchemaChange>(DALProviderManager.DALProvider.ConfigDALProvider.GetSchemaChangesApplied()
                .OrderBy(s => s.MajorReleaseNumber)
                .ThenBy(s => s.MinorReleaseNumber)
                .ThenBy(s => s.PointReleaseNumber).ToList());
        }

        public static Collection<ISchemaChange> GetSchemaChangesAvailable()
        {
            //Les fichiers ont le format 00.00.0000.sql ( major.minor.point.sql )
            var regex = new Regex("(?<major>[0-9]{2}).(?<minor>[0-9]{2}).(?<point>[0-9]{4}).sql$");
            var assembly = Assembly.GetExecutingAssembly();
            var assemblyName = assembly.GetName().Name;
            var resourcePrefix = "DB.ChangeScripts";

            var schemaChanges = assembly.GetManifestResourceNames()
                .Where(c => c.StartsWith(assemblyName + "." + resourcePrefix + "."))
                .Where(c => regex.IsMatch(c))
                .Select(c => GetSchemaChangeFromFilename(regex.Match(c)))
                .OrderBy(s => s.MajorReleaseNumber)
                .ThenBy(s => s.MinorReleaseNumber)
                .ThenBy(s => s.PointReleaseNumber);

            return new Collection<ISchemaChange>(schemaChanges.ToList());

        }

        public static ISchemaChange GetSchemaChangeFromScriptName(string pScriptName)
        {
            var schemaChange = GetSchemaChangesAvailable()
                .Where(c => c.ScriptName == pScriptName)
                .FirstOrDefault();

            return schemaChange;
        }

        public static Collection<ISchemaChange> GetSchemaChangesMerged()
        {
            var applied = GetSchemaChangesApplied();
            var available = GetSchemaChangesAvailable();

            var removed = new List<ISchemaChange>();
            foreach (var change in available)
            {
                if (applied.Contains(change, new SchemaChangeEqualityComparer()))
                {
                    removed.Add(change);
                }
            }

            var merged = available.Except(removed).Concat(applied)
                .OrderBy(s => s.MajorReleaseNumber)
                .ThenBy(s => s.MinorReleaseNumber)
                .ThenBy(s => s.PointReleaseNumber).ToList();

            return new Collection<ISchemaChange>(merged);
        }

        public static string GetSchemaChangeContent(ISchemaChange pSchemaChange)
        {
            var filename = pSchemaChange.ScriptName;
            if (string.IsNullOrEmpty(filename))
            {
                return string.Empty;
            }
            var extension = Path.GetExtension(filename);

            using (StreamReader reader = Sugar.Resources.GetResourceReader(pSchemaChange.ResourceName, Assembly.GetExecutingAssembly()))
            {
                switch (extension)
                {
                    case ".zip":
                        using (ZipInputStream stream = new ZipInputStream(reader.BaseStream))
                        {
                            var file = stream.GetNextEntry();
                            StringBuilder builder = new StringBuilder();
                            int size = 2048;
                            byte[] data = new byte[2048];
                            while (true)
                            {
                                size = stream.Read(data, 0, data.Length);
                                if (size > 0)
                                {
                                    builder.Append(Encoding.Unicode.GetString(data));
                                }
                                else
                                {
                                    break;
                                }
                            }
                            return builder.ToString();
                        }
                    default:
                        return reader.ReadToEnd();

                }
            }
        }

        public static void ApplySchemaChange(ISchemaChange pSchemaChange)
        {
            var content = GetSchemaChangeContent(pSchemaChange);
            DALProviderManager.DALProvider.ConfigDALProvider.ApplySchemaChange(content);
            pSchemaChange.DateApplied = DateTime.Now;
            DALProviderManager.DALProvider.ConfigDALProvider.InsertSchemaChange(pSchemaChange);
        }

        /// <summary>
        /// Comparateur de ISchema Change
        /// Attention il ne prend pas en compte le AppliedDate
        /// </summary>
        private class SchemaChangeEqualityComparer : IEqualityComparer<ISchemaChange>
        {

            #region IEqualityComparer<ISchemaChange> Members

            public bool Equals(ISchemaChange x, ISchemaChange y)
            {
                return GetHashCode(x).CompareTo(GetHashCode(y)) == 0;
            }

            public int GetHashCode(ISchemaChange obj)
            {
                return obj.ScriptName.GetHashCode()
                    ^ obj.MajorReleaseNumber.GetHashCode()
                    ^ obj.MinorReleaseNumber.GetHashCode()
                    ^ obj.PointReleaseNumber.GetHashCode();
            }

            #endregion
        }

        private static ISchemaChange GetSchemaChangeFromFilename(Match pMatch)
        {
            var major = pMatch.Groups["major"].Value;
            var minor = pMatch.Groups["minor"].Value;
            var point = pMatch.Groups["point"].Value;


            return new SchemaChange()
            {
                MajorReleaseNumber = major,
                MinorReleaseNumber = minor,
                PointReleaseNumber = point,
                ScriptName = pMatch.Value
            };

        }

        #endregion

        #region Configurations

        public static IDictionary<String, String> GetAllConfigurations()
        {
            return Provider.AllConfigurations;
        }

        public static void SaveConfiguration(String key, String value)
        {
            DALProviderManager.DALProvider.ConfigDALProvider.SaveConfiguration(key, value);
            Provider.Reload();
        }

        public static void ReloadConfiguration()
        {
            Provider.Reload();
        }

        #endregion

        public static void RefreshUrlDictionary()
        {
            DALProviderManager.DALProvider.ConfigDALProvider.RefreshUrlDictionary();
        }
    }
}
